#ifndef XG_WEBX_STD_CPP
#define XG_WEBX_STD_CPP
///////////////////////////////////////////////////////////
#include "../std.h"

#ifndef XG_UPLOADFILE_REQUEST_MAXSZ
#define XG_UPLOADFILE_REQUEST_MAXSZ		(128 * 1024 * 1024)
#endif

typedef int (*HttpPluginInitFunc)(IHttpServer*);

string& HttpCgiSetup::GetCgiPath()
{
	static string path;

	return path;
}

int webx::ProcessBase::simpleResponse(int code)
{
	clearResponse();

	out << "{\"code\":" << code << "}";

	return out.size();
}
string webx::ProcessBase::checkLogin(const string& sid)
{
	checkSession(sid);

	auto check = [&](){
		user.clear();

		session->get("USER", user);

		if (user.empty()) return XG_TIMEOUT;

		return session->get("DBID", dbid) ? XG_OK : XG_TIMEOUT;
	};

	int res = check();
	
	if (res < 0)
	{
		user.clear();
		dbid.clear();

		stdx::Throw(res, res == XG_TIMEOUT ? "login timeout" : "check login failed");
	}

	return user;
}
void webx::ProcessBase::checkSession(const string& sid)
{
	auto check = [&](){
		string key = sid;

		if (key.empty() && (key = webx::GetSessionId(request)).empty()) return XG_TIMEOUT;

		session = webx::GetSession(key);

		return session ? XG_OK : XG_TIMEOUT;
	};
	
	int res = check();
	
	if (res < 0) stdx::Throw(res, res == XG_TIMEOUT ? "session timeout" : "check session failed");
}
void webx::ProcessBase::parse(JsonReflect& data, bool decode)
{
	string content = request->getDataString();

	if (content.front() == '{' || content.back() == '}')
	{
		data.fromString(decode ? stdx::DecodeURL(content) : content);
	}
	else
	{
		vector<ReflectItem> vec = ReflectHelper::GetAttrList(&data);

		for (ReflectItem& item : vec) item.set(&data, request->getParameter(item.getName()));
	}
}
void webx::ProcessBase::checkSystemRight(const string& grouplist)
{
	if (!session && grouplist.empty()) stdx::Throw(XG_AUTHFAIL, "permission denied");
	
	string group = "," + (grouplist.empty() ? session->get("GROUPLIST") : grouplist) + ",";

	if (group.find(",system,") == string::npos) return stdx::Throw(XG_AUTHFAIL, "permission denied");
}
int webx::ProcessBase::process()
{
	return XG_FAIL;
}
int webx::ProcessBase::forward(ProcessBase* cgi)
{
	int res = cgi->doWork(request, response);
	const string& content = cgi->getOutString();

	if (content.length() > 0) out << content;

	file = cgi->file;

	return res;
}
int webx::ProcessBase::doWork(HttpRequest* request, HttpResponse* response)
{
	int val = XG_SYSERR;

	this->request = request;
	this->response = response;

	try
	{
		val = func ? func() : process();
	}
	catch(const Exception& e)
	{
		LogTrace(eERR, "catch exception[%d][%s]", e.getErrorCode(), e.getErrorString());

		simpleResponse(e.getErrorCode());
	}
	catch(const exception& e)
	{
		LogTrace(eERR, "catch exception[%s]", e.what());

		simpleResponse(XG_SYSERR);
	}
	catch(...)
	{
		LogTrace(eERR, "catch unknown exception");

		simpleResponse(XG_SYSERR);
	}

	if (val < 0 && file.get() == NULL && out.empty()) return val;

	if (file) return file->size();

#ifdef HTTP_GZIP_SIZE
	if ((val = out.size()) < HTTP_GZIP_SIZE) return val;

	if (createFile(val))
	{
		u_int32 sz = val;
		
		if (GZIPCompress(out.str(), val, file->getData(), &sz) == 0 && sz < val)
		{
			response->setHeadValue("Content-Encoding", "gzip");
			file->truncate(sz);
			out.clear();

			return sz;
		}

		file = NULL;
	}
#endif
	return out.size();
}

bool webx::Application::main()
{
	LogThread::Instance()->init("log", 10 * 1024 * 1024, true);

	app = HttpServer::Instance();

	doWork();

	return true;
}
int webx::Application::doWork()
{
	int val = XG_SYSERR;

	try
	{
		val = func ? func() : process();
	}
	catch(const Exception& e)
	{
		LogTrace(eERR, "catch exception[%d][%s]", e.getErrorCode(), e.getErrorString());

		simpleResponse(e.getErrorCode());
	}
	catch(const exception& e)
	{
		LogTrace(eERR, "catch exception[%s]", e.what());

		simpleResponse(XG_SYSERR);
	}
	catch(...)
	{
		LogTrace(eERR, "catch unknown exception");

		simpleResponse(XG_SYSERR);
	}

	if (file && file->size() > 0) return fwrite(file->getData(), 1, file->size(), stdout);

	if (out.length() > 0)
	{
		cout << out.getContent();

		return out.length();
	}

	return val;
}
int webx::Application::process()
{
	return XG_FAIL;
}
string webx::Application::checkLogin()
{
	JsonElement data;

	return checkLogin(data);
}
int webx::Application::simpleResponse(int code)
{
	clearResponse();

	return out.printf("{\"code\":%d}", code).size();
}
string webx::Application::checkLogin(JsonElement& data)
{
	auto check = [&]{
		HttpRequest request("CheckLogin?flag=C");

		request.setHeadValue("Cookie", GetAppHeadParameter("Cookie"));

		sp<HttpResponse> response = HttpServer::Instance()->getLocaleResult(request);

		if (!response) return XG_SYSERR;

		SmartBuffer buffer = response->getResult();
		
		if (buffer.size() <= 0) return XG_SYSERR;
		
		if (data.init(buffer.str()) && data["code"].isNumber())
		{
			int code = data["code"].asInteger();
			
			if (code > 0)
			{
				if (data["user"].isString()) user = data["user"].asString();
				if (data["dbid"].isString()) dbid = data["dbid"].asString();
			}
		}

		return XG_DATAERR;
	};
	
	int res = check();
	
	if (res < 0)
	{
		user.clear();
		dbid.clear();

		stdx::Throw(res, "session timeout");
	}

	return user;
}
int webx::Application::forward(ProcessBase* cgi)
{
	int res = cgi->doWork(NULL, NULL);
	const string& content = cgi->getOutString();

	if (content.length() > 0) out << content;

	file = cgi->file;

	return res;
}
void webx::Application::parse(JsonReflect& data, bool decode)
{
	string content;

	if (Process::GetCmdParam(2)) content = Process::GetCmdParam(2);

	if (content.front() == '{' || content.back() == '}')
	{
		data.fromString(decode ? stdx::DecodeURL(content) : content);
	}
	else
	{
		vector<ReflectItem> vec = ReflectHelper::GetAttrList(&data);

		for (ReflectItem& item : vec) item.set(&data, GetAppDataParameter(item.getName()));
	}
}
void webx::Application::checkSystemRight(const string& grouplist)
{
	string group = grouplist;

	if (group.empty())
	{
		JsonElement data;

		checkLogin(data);

		group = data["grouplist"].asString();
	}

	group = "," + group + ",";

	if (group.find(",system,") == string::npos) return stdx::Throw(XG_AUTHFAIL, "permission denied");
}

string webx::GetCgiPathList()
{
	string path;
	static auto& appmap = *webx::GetAppMap();

	if (appmap.size() > 0)
	{
		for (auto& item : appmap) path += "|" + item.second.path;

		path = path.substr(1);
	}

	return path;
}

void webx::ReloadSystemConfig()
{
	typedef void (*ReloadFunc)();

	static ReloadFunc func = (ReloadFunc)Process::GetObject("HTTP_RELOAD_SYSTEM_CONFIG_FUNC");

	if (func) func();
}
	
map<string, webx::WebAppData>* webx::GetAppMap()
{
	static map<string, WebAppData> appmap;
	return &appmap;
}

sp<webx::ProcessBase> webx::GetWebApp(const string& path)
{
	string key = path;
	static auto& appmap = *GetAppMap();
	auto it = appmap.find(stdx::tolower(key));

	return it == appmap.end() ? sp<webx::ProcessBase>() : it->second.create();
}

string webx::GetAppHeadParameter(const string& key)
{
	static HttpHeadNode data;

	if (data.getKeys().empty() && Process::GetCmdParamCount() > 1)
	{
		data.parse(stdx::DecodeURL(Process::GetCmdParam(1)));
	}

	string val = data.getValue(key);

	if (val.length() > 0) return val;

	return data.getValue(stdx::tolower(val = key));
}
string webx::GetAppDataParameter(const string& key)
{
	static HttpDataNode data;
	
	if (data.getKeys().empty() && Process::GetCmdParamCount() > 2)
	{
		data.parse(Process::GetCmdParam(2));
	}
	
	string val = data.getValue(key);
	
	if (val.empty())
	{
		const char* cmd = Process::GetCmdParam("-" + key);
		
		if (cmd) return cmd;
	}
	
	return val;
}

bool webx::IsMailString(const string& str)
{
	if(str.length() < 4 || str.length() > 128) return false;
	
	size_t m = str.find('@');
	size_t n = str.rfind('.');
	
	if (m == string::npos || n == string::npos || m > n) return false;
	
	return true;
}

string webx::GetScriptString(const string& str)
{
	string msg = stdx::replace(str, "\\", "\\\\");

	msg = stdx::replace(msg, "\n", "\\n");
	msg = stdx::replace(msg, "\r", "\\r");
	msg = stdx::replace(msg, "\t", "\\t");
	msg = stdx::replace(msg, "\'", "\\\'");
	msg = stdx::replace(msg, "\"", "\\\"");

	return stdx::replace(msg, "</script>", "</'+'script>");
}

string webx::GetFileIcon(const string& filename)
{
	size_t pos = filename.rfind('.');
	static HttpServer* app = HttpServer::Instance();

	if (pos == string::npos) return "/res/img/file/ask.png";

	string url = "/res/img/file/";
	string ext = filename.substr(pos + 1);

	url += stdx::tolower(ext) + ".png";

	if (app->getCgiMapData(url).url.length() > 0) return url;

	if (ext == "png" || ext == "jpg" || ext == "gif" || ext == "bmp" || ext == "jpeg")
	{
		url = "/res/img/menu/image.png";
	}
	else if (ext == "h" || ext == "c" || ext == "cc" || ext == "cpp" || ext == "cxx")
	{
		url = "/res/img/file/cpp.png";
	}
	else if (ext == "zip" || ext == "rar" || ext == "tar" || ext == "gz" || ext == "7z")
	{
		url = "/res/img/file/zip.png";
	}
	else if (ext == "wav" || ext == "aac" || ext == "mid" || ext == "wma" || ext == "mp3")
	{
		url = "/res/img/file/wav.png";
	}
	else if (ext == "rmvb" || ext == "avi" || ext == "flv" || ext == "wmv" || ext == "mp4")
	{
		url = "/res/img/file/avi.png";
	}
	else if (ext == "exe" || ext == "dll" || ext == "lib" || ext == "apk" || ext == "so" || ext == "a")
	{
		url = "/res/img/file/exe.png";
	}
	else if (ext == "doc" || ext == "docx")
	{
		url = "/res/img/file/doc.png";
	}
	else if (ext == "xls" || ext == "xlsx")
	{
		url = "/res/img/file/xls.png";
	}
	else if (ext == "ppt" || ext == "pptx")
	{
		url = "/res/img/file/ppt.png";
	}
	else if (ext == "htm" || ext == "html")
	{
		url = "/res/img/file/htm.png";
	}
	else
	{
		url = "/res/img/file/ask.png";
	}

	if (app->getCgiMapData(url).url.length() > 0) return url;
	
	return "/res/img/file/ask.png";
}

string webx::GetLimitString(DBConnect* dbconn, int pagesize, int page)
{
	string str = dbconn->getSystemName();
	
	if (str == "SQLite")
	{
		stdx::format(str, " LIMIT %d OFFSET %d", pagesize, page * pagesize);
	}
	else if (str == "MySQL")
	{
		stdx::format(str, " LIMIT %d,%d", page * pagesize, pagesize);
	}
	else
	{
		str = stdx::EmptyString();
	}
	
	return str;
}

bool webx::IsFileName(const string& str, int minlen, int maxlen)
{
	int len = str.length();
	const char* errstr = "`'^*<>,:;?|%\r\n\t\"\\/";

	if (len <= 0) return minlen <= 0;

	CHECK_FALSE_RETURN(len >= minlen && len <= maxlen);

	--len;

	for (int i = 0; i < len; i++)
	{
		CHECK_FALSE_RETURN(strchr(errstr, str[i]) == NULL);
	}
	
	return str.find("..") == string::npos;
}

bool webx::IsFilePath(const string& str, int minlen, int maxlen)
{
	int len = str.length();
	const char* errstr = "`'^*<>,:;?|%\r\n\t\"";

	if (len <= 0) return minlen <= 0;

	CHECK_FALSE_RETURN(len >= minlen && len <= maxlen);

	const char* end = str.c_str();

	for (int i = 0; i < len; i++)
	{
		CHECK_FALSE_RETURN(strchr(errstr, str[i]) == NULL);
	}

	return str.find("..") == string::npos;
}

bool webx::IsAlnumString(const string& str, int minlen, int maxlen)
{
	int len = str.length();

	if (len <= 0) return minlen <= 0;

	CHECK_FALSE_RETURN(len >= minlen && len <= maxlen);

	return ::IsAlnumString(str.c_str());
}

bool webx::IsSeqnoString(const string& str, int minlen, int maxlen)
{
	return IsAlnumString(str, minlen, maxlen);
}

void webx::CheckFileName(const string& str, int minlen, int maxlen)
{
	if (!IsFileName(str, minlen, maxlen)) stdx::Throw(XG_PARAMERR, "parameter error");
}

void webx::CheckFilePath(const string& str, int minlen, int maxlen)
{
	if (!IsFilePath(str, minlen, maxlen)) stdx::Throw(XG_PARAMERR, "parameter error");
}

void webx::CheckAlnumString(const string& str, int minlen, int maxlen)
{
	if (!IsAlnumString(str, minlen, maxlen)) stdx::Throw(XG_PARAMERR, "parameter error");
}

void webx::CheckSeqnoString(const string& str, int minlen, int maxlen)
{
	CheckAlnumString(str, minlen, maxlen);
}

string webx::GetSessionId(HttpRequest* request)
{
	size_t pos = 0;
	string sid = request->getParameter("sid");

	if (sid.length() > 0) return sid;

	return request->getCookie("sid");
}

sp<Session> webx::GetSession(const string& key, int timeout)
{
	if (RedisConnect::CanUse())
	{
		sp<RedisSession> session = newsp<RedisSession>();

		if (session->init(key, timeout)) return session;

		if (timeout > 0)
		{
			LogTrace(eERR, "create session[" + key + "][" + stdx::str(timeout) + "] failed");
			
			stdx::Throw(XG_SYSERR, "create session failed");
		}

		int code = session->getErrorCode();

		if (code == XG_DATAERR || code == XG_NOTFOUND)
		{
			LogTrace(eINF, "session[" + key + "] timeout");
			
			return NULL;
		}

		stdx::Throw(XG_SYSERR, "create session failed");
	}

	return GetLocaleSession(key, timeout);
}

sp<Session> webx::GetLocaleSession(const string& key, int timeout)
{
	static HttpServer* app = HttpServer::Instance();
	sp<Session> session = app->getSession(key, timeout);
	
	if (!session)
	{
		if (timeout > 0)
		{
			LogTrace(eERR, "create session[" + key + "][" + stdx::str(timeout) + "] failed");
			
			stdx::Throw(XG_SYSERR, "create session failed");
		}

		LogTrace(eINF, "session[" + key + "] timeout");
	}

	return session;
}

void webx::SetSessionId(HttpResponse* response, const string& sid)
{
	response->addCookie("sid", sid, 90 * 24 * 3600);
}

bool webx::InitDatabase()
{
	string dbcfgpath = Process::GetEnv("DATABASE_CONFIGFILE_PATH");

	if (dbcfgpath.empty()) dbcfgpath = stdx::translate("$SOURCE_HOME/webapp/etc/dbconfig.lua");

	CHECK_FALSE_RETURN(HttpServer::Instance()->initDatabase(dbcfgpath));
	
	if (HttpServer::Instance()->getId() == 0)
	{
		webx::LoadHttpPlugin(HttpServer::Instance()->getPath() + "etc/plugin/bin/MenuModule.so");
		webx::LoadHttpPlugin(HttpServer::Instance()->getPath() + "etc/plugin/bin/RouteModule.so");
	}
	
	return true;
}

int webx::LoadHttpPlugin(const string& path)
{
	int val;
	sp<DllFile> dll = DllFile::Get(path);
	HttpPluginInitFunc HttpPluginInit = NULL;

	if (!dll)
	{
		LogTrace(eERR, "load plugin[%s] failed", path.c_str());

		return XG_FAIL;
	}
	
	if (dll->read(HttpPluginInit, "HttpPluginInit"))
	{
		val = HttpPluginInit(HttpServer::Instance());
	}
	else
	{
		val = XG_SYSERR;
	}
	
	if (val < 0)
	{
		LogTrace(eERR, "initialize plugin[%s] failed[%d]", path.c_str(), val);
	}
	else
	{
		LogTrace(eIMP, "initialize plugin[%s] success", path.c_str());
	}
	
	return val;
}

int webx::SaveParamItem(FileParamItem& item, const string& path)
{
	if (item.len < 0) return item.len;

	if (item.filename.empty())
	{
		item.filepath = string(item.data, item.data + item.len);
			
		return item.len;
	}
	else
	{
		size_t pos;
		string extnm;

		if ((pos = item.filename.rfind('.')) == string::npos)
		{
			extnm = ".dat";
		}
		else
		{
			if ((extnm = item.filename.substr(pos)).length() <= 1) extnm = ".dat";
		}

		while (true)
		{
			item.filepath = path + "/dat/" + DateTime::GetBizId() + extnm;
			item.filepath = stdx::replace(item.filepath, "//", "/");
			
			if (path::type(item.filepath) <= eNONE) break;

			Sleep(1);
		}

		XFile file;

		if (file.create(item.filepath))
		{
			return item.len > 0 ? file.write(item.data, item.len) : item.len;
		}
	}

	return XG_SYSERR;
}

int webx::GetFileParamList(vector<FileParamItem>& vec, SmartBuffer& buffer, HttpServer* app, HttpRequest* request, HttpResponse* response)
{
	int sz = request->getDataSize();
	sp<Socket> sock = response->getSocket();
	string boundary = request->getBoundary();
	SmartBuffer content = request->getContent();
	int readed = content.size() - request->getHeadSize();
	
	if (readed < 0 || sz <= 0 || readed > sz || sz > XG_UPLOADFILE_REQUEST_MAXSZ || boundary.empty()) return XG_DATAERR;

	memmove(content.str(), content.str() + request->getHeadSize(), readed);
	content.truncate(readed);

	int len = 0;
	int timeout = app->getTimeout();

	if (readed == sz)
	{
		buffer = content;
	}
	else
	{
		time_t utime = time(NULL);

		memcpy(buffer.malloc(sz), content.str(), readed);

		while (readed < sz)
		{
			if ((len = sock->read(buffer.str() + readed, buffer.size() - readed, false)) < 0) return len;

			if (len < SOCKET_TIMEOUT_LIMITSIZE)
			{
				if (utime + timeout < time(NULL)) return XG_TIMEOUT;

				Sleep(10);
			}

			utime = time(NULL);

			readed += len;
		}
	}

	int res;
	string msg;
	ContentNode node;
	FileParamItem item;
	const char* end = NULL;
	const char* endtag = "\r\n";
	string tag = "--" + boundary;
	const char* str = buffer.str();
	const size_t endtaglen = strlen(endtag);

	if (memcmp(str, tag.c_str(), tag.length())) return XG_DATAERR;

	str += tag.length() + endtaglen;

	auto getTrimPairString = [](const string& str, char ch){
		return (str.length() >= 2 && str[0] == ch && str.back() == ch) ? str.substr(1, str.length() - 2) : str;
	};
	
	while (true)
	{
		end = std::search(str, (const char*)(buffer.str()) + buffer.size(), endtag, endtag + endtaglen);

		if (end == (const char*)(buffer.str()) + buffer.size()) return XG_DATAERR;

		node.setEndSpliter(";");
		node.setKeySpliter("=");
		msg = stdx::replace(string(str, end), " ", "");
		
		if (msg.empty()) return XG_DATAERR;

		node.parse(msg.c_str());

		item.key = getTrimPairString(node.getValue("name"), '\"');

		if (item.key.empty()) return XG_DATAERR;

		item.filename = getTrimPairString(node.getValue("filename"), '\"');
		str = end + endtaglen;

		end = std::search(str, (const char*)(buffer.str()) + buffer.size(), endtag, endtag + endtaglen);

		if (end == (const char*)(buffer.str()) + buffer.size()) return XG_DATAERR;

		node.setEndSpliter(";");
		node.setKeySpliter(":");
		msg = stdx::replace(string(str, end), " ", "");

		if (msg.empty())
		{
			item.contype.clear();
		}
		else
		{
			node.parse(msg.c_str());
			item.contype = node.getValue("Content-Type");

			if (item.contype.empty()) item.contype = "text/plain";
		}
		
		item.data = str = end + endtaglen;

		end = std::search(str, (const char*)(buffer.str()) + buffer.size(), tag.c_str(), tag.c_str() + tag.length());
		
		if (end == (const char*)(buffer.str()) + buffer.size()) return XG_DATAERR;

		if (item.contype.empty())
		{
			item.len = (end - str) - 2;
		}
		else
		{
			item.len = (end - str) - 4;
			item.data += endtaglen;
		}

		if ((res = SaveParamItem(item, app->getPath())) < 0)
		{
			if (item.filename.length() > 0) LogTrace(eIMP, "recv file[" + item.filename + "] failed");

			return res;
		}
		
		if (item.filename.length() > 0)
		{
			LogTrace(eINF, "recv file[" + item.filename + "] success");

			vec.push_back(item);
		}

		str = end + tag.length();
		
		if (str + endtaglen >= (const char*)(buffer.str()) + buffer.size()) break;

		if (memcmp(str, endtag, endtaglen)) break;

		str += endtaglen;
	}

	return vec.size();
}

int webx::PackJson(sp<QueryResult> rs, JsonElement& json)
{
	return PackJson(rs, json, [](int idx, const string& data){
		return data;
	});
}

int webx::PackJson(sp<QueryResult> rs, const string& name, JsonElement& json)
{
	return PackJson(rs, name, json, [](int idx, const string& data){
		return data;
	});
}

int webx::PackJson(sp<QueryResult> rs, JsonElement& json, function<string(int, const string&)> func)
{
	if (!rs) return XG_ERROR;

	int sz = 0;
	sp<RowData> row;
	vector<string> vec;
	int colsz = rs->cols();
	
	if (colsz <= 0) return XG_ERROR;

	for (int i = 0; i < colsz; i++)
	{
		string name = rs->getColumnName(i);
		vec.push_back(stdx::tolower(name));
	}

	if (row = rs->next())
	{
		for (int i = 0; i < colsz; i++)
		{
			json[vec[i]] = func(i, row->getString(i));
		}
	}

	return sz;
}

int webx::PackJson(sp<QueryResult> rs, const string& name, JsonElement& json, function<string(int, const string&)> func)
{
	if (!rs) return XG_ERROR;

	int sz = 0;
	sp<RowData> row;
	vector<string> vec;
	int colsz = rs->cols();
	JsonElement list = json.addArray(name);

	if (list.isArray()) sz = list.size();

	if (colsz <= 0) return XG_ERROR;

	for (int i = 0; i < colsz; i++)
	{
		string name = rs->getColumnName(i);
		vec.push_back(stdx::tolower(name));
	}

	while (row = rs->next())
	{
		JsonElement item = list[sz++];

		for (int i = 0; i < colsz; i++)
		{
			item[vec[i]] = func(i, row->getString(i));
		}
	}

	return sz;
}
///////////////////////////////////////////////////////////
#endif
